/**
日志库
1. 支持往不同的地方输出日志
2. 日志分级别
	1. Debug
	2. Trace
	3. Info
	4. Warning
	5. Error
	6. Fatal
3. 日志要支持开关控制 支持级别
4. 完整的日志记录要包含有时间，行号，文件名，日志级别，日志信息
5. 日志文件要切割
*/
package main

import (
	"errors"
	"fmt"
	"os"
	"path"
	"runtime"
	"strings"
	"time"
)

const (
	DEBUG LogLeve = iota
	TRACE
	INFO
	WARNING
	ERROR
	FATAL
)

type LogLeve uint16

type Log struct {
	path    string   // 日志文件存放路径
	name    string   // 日志文件存放名称
	lv      LogLeve  // 级别
	chunk   int64    // 文件切割大小
	fileObj *os.File // 文件对象
}

func getTime() string {
	now := time.Now()
	return now.Format("2006-01-02 15:04:05")
}

func getCaller() (file string, line int) {
	_, file, line, ok := runtime.Caller(3) // 参数为上溯的栈帧数，0表示Caller的调用者（Caller所在的调用栈）1表示上一层函数调用的地方，2表示上上层，依次类推
	if !ok {
		panic("get celler failed!")
	}
	return file, line
}

func parseLogLeve(lv string) LogLeve {
	switch strings.ToUpper(lv) {
	case "DEBUG":
		return DEBUG
	case "TRACE":
		return TRACE
	case "INFO":
		return INFO
	case "WARNING":
		return WARNING
	case "ERROR":
		return ERROR
	case "FATAL":
		return FATAL
	default:
		return DEBUG
	}
}
func stringifyLogLeve(lv LogLeve) (error, string) {
	switch lv {
	case DEBUG:
		return nil, "DEBUG"
	case TRACE:
		return nil, "TRACE"
	case INFO:
		return nil, "INFO"
	case WARNING:
		return nil, "WARNING"
	case ERROR:
		return nil, "ERROR"
	case FATAL:
		return nil, "FATAL"
	default:
		err := errors.New("解析日志级别错误")
		return err, ""
	}
}

func NewLog(path, name string, lv string, chunk int64) *Log {
	log_lv := parseLogLeve(lv)
	log := &Log{
		path:  path,
		name:  name,
		lv:    log_lv,
		chunk: chunk,
	}
	log.initFile() // 初始化文件 打开
	return log
}

func (l *Log) initFile() {
	fullFileName := path.Join(l.path, l.name)
	file, err := os.OpenFile(fullFileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
	if err != nil {
		fmt.Printf("open log file failed,err:%s", err)
		return
	}
	l.fileObj = file
}

func (l *Log) checkSplit() bool {
	fileInfo, err := l.fileObj.Stat()
	if err != nil {
		fmt.Printf("get file info failed,err:%s", err)
	}
	size := fileInfo.Size()
	return l.chunk <= size
}

func (l *Log) write(loglv LogLeve, format string, a ...interface{}) {
	file, line := getCaller()
	msg := fmt.Sprintf(format, a...) // 支持传入格式化的信息
	err, lv := stringifyLogLeve(loglv)
	if err != nil {
		panic(err)
	}
	// 文件切割 按文件大小
	if l.checkSplit() {
		// 切割文件（首先关闭当前文件，然后备份当前文件，最后打开新的文件将文件指定给f.fileObj）
		fullFileName := path.Join(l.path, l.name)
		l.fileObj.Close()
		now := time.Now()
		err := os.Rename(fullFileName, fullFileName+now.Format("20060102150405")+".bak")
		if err != nil {
			fmt.Print(err)
		}
		f, _ := os.OpenFile(fullFileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
		l.fileObj = f
	}
	// 级别控制
	if l.lv <= loglv {
		fmt.Fprintf(l.fileObj, "[%s %s line:%d %s] %s\n", getTime(), file, line, lv, msg) // 写入文件中
	}
}

func (l *Log) Debug(format string, a ...interface{}) {
	l.write(DEBUG, format, a...)
}
func (l *Log) Trace(format string, a ...interface{}) {
	l.write(TRACE, format, a...)
}
func (l *Log) Info(format string, a ...interface{}) {
	l.write(INFO, format, a...)
}
func (l *Log) Warning(format string, a ...interface{}) {
	l.write(WARNING, format, a...)
}
func (l *Log) Error(format string, a ...interface{}) {
	l.write(ERROR, format, a...)
}
func (l *Log) Fatal(format string, a ...interface{}) {
	l.write(FATAL, format, a...)
}

func main() {

	mylog := NewLog("F:\\go-demo\\logger", "go.log", "info", 1024*1024*10) // 10MB
	for {
		mylog.Debug("666%s", "66")
		mylog.Trace("666%s", "66")
		mylog.Info("666%s", "66")
		mylog.Warning("666%s", "66")
		mylog.Error("666%s", "66")
		mylog.Fatal("666%s", "66")
		time.Sleep(2)
	}

}
